package drds.datasource.connection_pool.impl.thread;

import drds.datasource.connection_pool.impl.DataSource;
import drds.datasource.connection_pool.util.JdbcUtils;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CreateConnectionTask implements Runnable {
    /**
     * 表示不是由CreateConnectionRunnable进行创建的
     */
    public static final long create_connection_task_id = 0;
    public final long createConnectionTaskId;

    private final DataSource dataSource;
    private int errorCount = 0;
    private boolean initTask = false;


    public CreateConnectionTask(DataSource dataSource, boolean initTask) {
        this.dataSource = dataSource;
        createConnectionTaskId = DataSource.createConnectionTaskRunnableIdSeedUpdater.getAndIncrement(this.dataSource);
        this.initTask = initTask;
    }

    @Override
    public void run() {
        runInternal();
    }

    private void runInternal() {
        for (; ; ) {

            // addLast
            this.dataSource.lock.lock();
            try {
                if (this.dataSource.closed || this.dataSource.closing) {
                    this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                    return;
                }

                boolean emptyWait = true;

                if (this.dataSource.createError != null && this.dataSource.poolingCount == 0) {
                    emptyWait = false;
                }

                if (emptyWait) {
                    // 必须存在线程等待，才创建连接
                    if (this.dataSource.poolingCount >= this.dataSource.notEmptyWaitThreadCount
                            && (!(this.dataSource.keepAlive && this.dataSource.activeCount
                            + this.dataSource.poolingCount < this.dataSource.minIdle)) // 在keepAlive场景不能放弃创建
                            && (!initTask) // 线程池初始化时的任务不能放弃创建
                            && !this.dataSource.isFailContinuous() // failContinuous时不能放弃创建，否则会无法创建线程
                    ) {
                        this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                        return;
                    }

                    // 防止创建超过maxActive数量的连接
                    if (this.dataSource.activeCount
                            + this.dataSource.poolingCount >= this.dataSource.maxActive) {
                        this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                        return;
                    }
                }
            } finally {
                this.dataSource.lock.unlock();
            }

            Connection connection = null;

            try {
                connection = this.dataSource.createConnection();
            } catch (OutOfMemoryError e) {
                log.error("create connection OutOfMemoryError, out memory. ", e);

                errorCount++;
                if (errorCount > this.dataSource.connectionErrorRetryAttempts
                        && this.dataSource.timeBetweenConnectErrorMillis > 0) {
                    // fail over retry attempts
                    this.dataSource.setFailContinuous(true);
                    if (this.dataSource.failFast) {
                        this.dataSource.lock.lock();
                        try {
                            this.dataSource.notEmpty.signalAll();
                        } finally {
                            this.dataSource.lock.unlock();
                        }
                    }

                    if (this.dataSource.breakAfterAcquireFailure) {
                        this.dataSource.lock.lock();
                        try {
                            this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                        } finally {
                            this.dataSource.lock.unlock();
                        }
                        return;
                    }

                    this.errorCount = 0; // reset errorCount
                    if (this.dataSource.closing || this.dataSource.closed) {
                        this.dataSource.lock.lock();
                        try {
                            this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                        } finally {
                            this.dataSource.lock.unlock();
                        }
                        return;
                    }

                    this.dataSource.createSchedulerFuture = this.dataSource.createScheduler.schedule(this,
                            this.dataSource.timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS);
                    return;
                }
            } catch (SQLException e) {
                log.error("create connection SQLException, url: " + this.dataSource.jdbcUrl, e);

                errorCount++;
                if (errorCount > this.dataSource.connectionErrorRetryAttempts
                        && this.dataSource.timeBetweenConnectErrorMillis > 0) {
                    // fail over retry attempts
                    this.dataSource.setFailContinuous(true);
                    if (this.dataSource.failFast) {
                        this.dataSource.lock.lock();
                        try {
                            this.dataSource.notEmpty.signalAll();
                        } finally {
                            this.dataSource.lock.unlock();
                        }
                    }

                    if (this.dataSource.breakAfterAcquireFailure) {
                        this.dataSource.lock.lock();
                        try {
                            this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                        } finally {
                            this.dataSource.lock.unlock();
                        }
                        return;
                    }

                    this.errorCount = 0; // reset errorCount
                    if (this.dataSource.closing || this.dataSource.closed) {
                        this.dataSource.lock.lock();
                        try {
                            this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                        } finally {
                            this.dataSource.lock.unlock();
                        }
                        return;
                    }

                    this.dataSource.createSchedulerFuture = this.dataSource.createScheduler.schedule(this,
                            this.dataSource.timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS);
                    return;
                }
            } catch (RuntimeException e) {
                log.error("create connection RuntimeException", e);
                // unknow fatal sql_exception
                this.dataSource.setFailContinuous(true);
                continue;
            } catch (Error e) {
                this.dataSource.lock.lock();
                try {
                    this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                } finally {
                    this.dataSource.lock.unlock();
                }
                log.error("create connection Error", e);
                // unknow fatal sql_exception
                this.dataSource.setFailContinuous(true);
                break;
            } catch (Throwable e) {
                this.dataSource.lock.lock();
                try {
                    this.dataSource.clearCreateConnectionTaskId(createConnectionTaskId);
                } finally {
                    this.dataSource.lock.unlock();
                }

                log.error("create connection unexecpted error.", e);
                break;
            }

            if (connection == null) {
                continue;
            }
            boolean result = this.dataSource.put(connection, createConnectionTaskId);
            if (!result) {
                try {
                    JdbcUtils.close(connection);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                log.info("failed to put connection。");
            }
            break;
        }
    }
}